﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Text; 
using XQ.Extension.EmitMapper.EmitInvoker;

namespace XQ.Extension.EmitMapper
{
    public class StaticConvertersManager
    {
        private class TypesPair
        {
            public Type typeFrom;

            public Type typeTo;

            public override int GetHashCode()
            {
                return typeFrom.GetHashCode() + typeTo.GetHashCode();
            }

            public override bool Equals(object obj)
            {
                TypesPair typesPair = (TypesPair)obj;
                if (typeFrom == typesPair.typeFrom)
                {
                    return typeTo == typesPair.typeTo;
                }

                return false;
            }

            public override string ToString()
            {
                return typeFrom.ToString() + " -> " + typeTo.ToString();
            }
        }

        private static StaticConvertersManager _defaultInstance;

        private Dictionary<TypesPair, MethodInfo> _typesMethods = new Dictionary<TypesPair, MethodInfo>();

        private List<Func<Type, Type, MethodInfo>> _typesMethodsFunc = new List<Func<Type, Type, MethodInfo>>();

        private static Dictionary<MethodInfo, Func<object, object>> _convertersFunc = new Dictionary<MethodInfo, Func<object, object>>();

        public static StaticConvertersManager DefaultInstance
        {
            get
            {
                if (_defaultInstance == null)
                {
                    lock (typeof(StaticConvertersManager))
                    {
                        if (_defaultInstance == null)
                        {
                            _defaultInstance = new StaticConvertersManager();
                            _defaultInstance.AddConverterClass(typeof(Convert));
                            _defaultInstance.AddConverterClass(typeof(EMConvert));
                            _defaultInstance.AddConverterClass(typeof(NullableConverter));
                            _defaultInstance.AddConverterFunc(EMConvert.GetConversionMethod);
                        }
                    }
                }

                return _defaultInstance;
            }
        }

        public void AddConverterClass(Type converterClass)
        {
            MethodInfo[] methods = converterClass.GetMethods(BindingFlags.Static | BindingFlags.Public);
            foreach (MethodInfo methodInfo in methods)
            {
                ParameterInfo[] parameters = methodInfo.GetParameters();
                if (parameters.Length == 1 && methodInfo.ReturnType != typeof(void))
                {
                    _typesMethods[new TypesPair
                    {
                        typeFrom = parameters[0].ParameterType,
                        typeTo = methodInfo.ReturnType
                    }] = methodInfo;
                }
            }
        }

        public void AddConverterFunc(Func<Type, Type, MethodInfo> converterFunc)
        {
            _typesMethodsFunc.Add(converterFunc);
        }

        public MethodInfo GetStaticConverter(Type from, Type to)
        {
            if (from == null || to == null)
            {
                return null;
            }

            foreach (Func<Type, Type, MethodInfo> item in Enumerable.Reverse(_typesMethodsFunc))
            {
                MethodInfo methodInfo = item(from, to);
                if (methodInfo != null)
                {
                    return methodInfo;
                }
            }

            MethodInfo value = null;
            _typesMethods.TryGetValue(new TypesPair
            {
                typeFrom = from,
                typeTo = to
            }, out value);
            return value;
        }

        public Func<object, object> GetStaticConverterFunc(Type from, Type to)
        {
            MethodInfo staticConverter = GetStaticConverter(from, to);
            if (staticConverter == null)
            {
                return null;
            }

            lock (_convertersFunc)
            {
                Func<object, object> value = null;
                if (_convertersFunc.TryGetValue(staticConverter, out value))
                {
                    return value;
                }

                value = ((MethodInvokerFunc_1)MethodInvoker.GetMethodInvoker(null, staticConverter)).CallFunc;
                _convertersFunc.Add(staticConverter, value);
                return value;
            }
        }
    }
}
